#include "ET++.ph"
#ifdef __GNUG__
#pragma implementation
#endif

#include "DevBitmap.h"

#include "WindowPort.h"
#include "WindowColorMap.h"
#include "Error.h"
#include "StreamBuf.h"
#include "ColorMapper.h"
#include "Class.h"

#define CACHESIZE 50

struct BitmapCache {
	int clock, wcmid;
	DevBitmap *org, *dbm;
} Cache[CACHESIZE];

static int cacheclock;
static int lastix;

inline int PaddedBytesPerLine(int w, int depth, u_short padbytes)
{
	int padbits= padbytes*8;
	return (((w*depth)+(padbits-1))/(padbits))*padbytes;
}

BitmapInfo::BitmapInfo(const Point &e, int bpp, int numcomp, int pad) : extent(e)
{
	bitsperpixel= bpp;
	numcomponents= numcomp;
	rowpad= pad;
	rowlen= PaddedBytesPerLine(extent.x, bitsperpixel*numcomponents, rowpad);
}

BitmapInfo::BitmapInfo(const Point &e, int depth, int pad) : extent(e)
{
	bitsperpixel= depth;
	rowpad= pad;
	rowlen= PaddedBytesPerLine(extent.x, bitsperpixel, rowpad);
}

//---- DevBitmap ---------------------------------------------------------------

NewMetaImpl(DevBitmap,RefCounted, (T(size), T(depth), TB(changed)));

DevBitmap::DevBitmap(const Point &sz, u_short dep) : size(sz)
{
	depth= dep;
	changed= FALSE;
}

DevBitmap::DevBitmap(DevBitmap *dbm) : size(dbm->size)
{
	depth= dbm->depth;
	changed= FALSE;
}

DevBitmap::~DevBitmap()
{
	register BitmapCache *bmp= Cache;
	for (int i= 0; i < CACHESIZE; i++, bmp++) {
		if (bmp->org == this || bmp->dbm == this) {
			DevBitmap *o1= bmp->org, *o2= bmp->dbm;
			bmp->org= bmp->dbm= 0;
			SafeUnref(o1);
			SafeUnref(o2);
			bmp->org= 0;
			bmp->wcmid= 0;
			bmp->clock= 0;
		}
	}
}

DevBitmap *DevBitmap::MakeCopy()
{
	DevBitmap *dbm= DevAllocBitmap(size, depth);
	dbm->DevBitBlt(0, 0, size.x, size.y, eBitCopy, this, 0, 0);
	return dbm;
}

int DevBitmap::BytesPerLine()
{
	return PaddedBytesPerLine(size.x, depth, 1);
}

void DevBitmap::ReadData(BitmapInfo *bi, IStream &is)
{
	int bpl= ((size.x*bi->bitsperpixel-1)/8+1);
	byte *bp= new byte[bpl];
	int y;
	for (y= 0; y < size.y; y++) {
		is.read(bp, bpl);
		SetRow(bi, y, bp);
	}
	delete bp;
}

void DevBitmap::SetData(BitmapInfo *bi, void *data)
{
	byte *b= (byte*) data;
	register int y;                                                                                                                                                                                                                                   
	for (y= 0; y < bi->extent.y; y++) {
		SetRow(bi, y, &b[y*bi->rowlen]);
		//data+= bi->rowlen;
	}
}

void DevBitmap::SetRow(BitmapInfo *bi, int y, byte *rowdata)
{
	register u_long pixel, m;
	register int x, i= 0, np, j;
	register byte *bp= rowdata;

	switch (bi->bitsperpixel) {
	case 1:
		for (x= 0; x < bi->rowlen; x++) {
			pixel= *bp++;
			m= 0x80;
			for (j= 0; j < 8; j++) {
				SetPixel(i++, y, (pixel & m) ? 1 : 0);
				m >>= 1;
			}
		}
		break;
	case 2:
		for (x= 0; x < bi->rowlen; x++) {
			pixel= *bp++;
			SetPixel(i++, y, (pixel & 0xc0) >> 6);
			SetPixel(i++, y, (pixel & 0x30) >> 4);
			SetPixel(i++, y, (pixel & 0x0c) >> 2);
			SetPixel(i++, y, (pixel & 0x03));
		}
		break;
	case 4:
		for (x= 0; x < bi->rowlen; x++) {
			pixel= *bp++;
			SetPixel(i++, y, (pixel & 0xf0) >> 4);
			SetPixel(i++, y, (pixel & 0x0f));
		}
		break;
	case 8:
		for (x= 0; x < bi->extent.x; x++)
			SetPixel(x, y, *bp++);
		break;
	case 16:
		{
			u_short *sp= (u_short*) bp;
			for (x= 0; x < bi->extent.x; x++)
				SetPixel(x, y, *sp++);
		}
		break;
	case 24:
		np= bi->bitsperpixel/8;
		for (x= 0; x < bi->extent.x; x++) {
			pixel= 0;
			for (j= 0; j < np; j++)
				pixel= (pixel << 8) + *bp++;
			SetPixel(i++, y, pixel);
		}
		break;
	case 32:
		{
			u_long *lp= (u_long*) bp;
			for (x= 0; x < bi->extent.x; x++)
				SetPixel(x, y, *lp++);
		}
		break;
	}
}

void DevBitmap::GetRow(BitmapInfo *bi, int row, byte *rowdata)
{
	register u_long pixel;
	register int x, y= row, sh;
	register byte *bp= rowdata;

	switch (bi->bitsperpixel) {
	case 1:
	case 2:
	case 4:
		*bp= 0;
		sh= 8;
		for (x= 0; x < size.x; x++) {
			pixel= GetPixel(x, y);
			sh-= bi->bitsperpixel;
			*bp |= (byte)(pixel << sh);
			if (sh == 0) {
				*++bp= 0;
				sh= 8;
			}
		}
		if (sh != 8)
			bp++;
		break;
	case 8:
		for (x= 0; x < size.x; x++) {
			pixel= GetPixel(x, y);
			*bp++= (byte)(pixel & 0x000000ff);
		}
		break;
	case 16:
		for (x= 0; x < size.x; x++) {
			pixel= GetPixel(x, y);
			*bp++= (byte)((pixel & 0x0000ff00) >> 8);
			*bp++= (byte)(pixel & 0x000000ff);
		}
		break;
	case 24:
		for (x= 0; x < size.x; x++) {
			pixel= GetPixel(x, y);
			*bp++= (byte)((pixel & 0x00ff0000) >> 16);
			*bp++= (byte)((pixel & 0x0000ff00) >> 8);
			*bp++= (byte)(pixel & 0x000000ff);
		}
		break;
	case 32:
		for (x= 0; x < size.x; x++) {
			pixel= GetPixel(x, y);
			*bp++= (byte)((pixel & 0xff000000) >> 24);
			*bp++= (byte)((pixel & 0x00ff0000) >> 16);
			*bp++= (byte)((pixel & 0x0000ff00) >> 8);
			*bp++= (byte)(pixel & 0x000000ff);
		}
		break;
	}
}

void DevBitmap::WriteData(BitmapInfo *bi, OStream &os)
{
	int bpl= ((size.x*bi->bitsperpixel-1)/8+1);
	byte *bp= new byte[bpl+100];
	register int y;
	for (y= 0; y < size.y; y++) {
		GetRow(bi, y, bp);
		os.write(bp, bpl);
	}
	delete bp;
}

void DevBitmap::DevBitBlt(int x0, int y0, int w, int h, BitBltOp op,
								DevBitmap *s, int sx, int sy, u_long val)
{
	register int x, y;

	for (y= 0; y < h; y++) {
		for (x= 0; x < w; x++) {
			switch (op) {
			case eBitCopy:
				SetPixel(x0+x, y0+y, s->GetPixel(sx+x, sy+y));
				break;
			case eBitOr:
				SetPixel(x0+x, y0+y,
						GetPixel(x0+x, y0+y) | s->GetPixel(sx+x, sy+y));
				break;
			case eBitSet:
				SetPixel(x0+x, y0+y, val);
				break;
			}
		}
	}
}

DevBitmap *DevBitmap::DevScaleBitmap(const Point &ss)
{
	int x= size.x, y= size.y, sx= ss.x, sy= ss.y;
	DevBitmap *pr1, *pr2;
	register int b, src, dst, d, a1, a2;
	float scale;

	if (sx == x)                // same width
		pr1= this;
	else if (sx < x) {          // x-shrink
		pr1= DevAllocBitmap(Point(sx, y), depth);
		if (depth > 1) {
			scale= (float)x/(float)sx;
			for (dst= 0; dst < sx; dst++)
				if ((b= (int)(dst*scale+0.5)) < x)
					pr1->DevBitBlt(dst, 0, 1, y, eBitCopy, this, b, 0);
		} else {
			scale= (float)sx/(float)x;
			for (src= 0; src < x; src++)
				if ((b= (int)(src*scale+0.5)) < sx)
					pr1->DevBitBlt(b, 0, 1, y, eBitOr, this, src, 0);
		}
	} else {                    // x-magnify
		pr1= DevAllocBitmap(Point(sx, y), depth);
		d= sx/x;
		scale= (float)sx/(float)x;
		for (src= 0; src < x; src++)        // slice
			if ((b= (int)(src*scale+0.5)) < sx)
				pr1->DevBitBlt(b, 0, 1, y, eBitCopy, this, src, 0);
		for (src= 0; src < d-1; src++)      // smear
			pr1->DevBitBlt(src+1, 0, sx-d+1, y, eBitOr, pr1, src, 0);
		for (a1= src= 0; src < x; src++) {  // fill remaining slots
			a2= (int)((src+1)*scale+0.5);
			if (a1+d != a2)
				pr1->DevBitBlt(a2-1, 0, 1, y, eBitCopy, this, src, 0);
			a1= a2;
		}
	}

	if (sy == y)                // same height
		pr2= pr1;
	else if (sy < y) {          // y-shrink
		pr2= DevAllocBitmap(Point(sx, sy), depth);
		if (depth > 1) {
			scale= (float)y/(float)sy;
			for (dst= 0; dst < sy; dst++)
				if ((b= (int)(dst*scale+0.5)) < y)
					pr2->DevBitBlt(0, dst, sx, 1, eBitCopy, pr1, 0, b);
		} else {
			scale= (float)sy/(float)y;
			for (src= 0; src < y; src++)
				if ((b= (int)(src*scale+0.5)) < sy)
					pr2->DevBitBlt(0, b, sx, 1, eBitOr, pr1, 0, src);
		}
		if (pr1 != this)
			delete pr1;
	} else if (sy > y) {        // y-magnify
		pr2= DevAllocBitmap(Point(sx, sy), depth);
		d= sy/y;
		scale= (float)sy/(float)y;
		for (src= 0; src < y; src++)        // slice
			if ((b= (int)(src*scale+0.5)) < sy)
				pr2->DevBitBlt(0, b, sx, 1, eBitCopy, pr1, 0, src);
		for (src= 0; src < d-1; src++)      // smear
			pr2->DevBitBlt(0, src+1, sx, sy-d+1, eBitOr, pr2, 0, src);
		for (a1= src= 0; src < y; src++) {  // fill remaining slots
			a2= (int)((src+1)*scale+0.5);
			if (a1+d != a2)
				pr2->DevBitBlt(0, a2-1, sx, 1, eBitCopy, pr1, 0, src);
			a1= a2;
		}
		if (pr1 != this)
			delete pr1;
	}
	return pr2;
}

void DevBitmap::DevHalftone(WindowColorMap *wcm, DevBitmap *spr,
														ColorMapper *cmapper)
{
	register int *next, v, error;
	int s= (int)cmapper->MaxPixel()+1, *level, *errors, i, x, y, incr, lasterror, *nextbuf;
	u_long *out;
	RGB result;

	level= new int[s];

	for (i= 0; i < s; i++) {
		cmapper->Pixel2RGB(i, &result);
		level[i]= result.AsGreyLevel();
	}

	out= new u_long[256];
	errors= new int[256];

	for (i= 0; i < 256; i++) {
		RGB rgb((short)i);
		out[i]= wcm->RGB2Index(&rgb, &result);
		errors[i]= i - result.AsGreyLevel();
	}

	nextbuf= new int[size.x + 4];
	next= &nextbuf[2];

	for (y= 0; y < size.y; y++) {
		if (y & 1) {   // scan left
			incr= 1;
			i= size.x-1;
		} else {        // scan right
			incr= -1;
			i= 0;
		}
		for (lasterror= x= 0; x < size.x; x++, i-= incr) {
			v= lasterror + level[spr->GetPixel(i, y)];
			if (v < 0)
				v= 0;
			else if (v > 255)
				v= 255;
			SetPixel(i, y, out[v]);
			error= errors[v];
			lasterror= next[i-incr] + (error*7) / 16;
			next[i-incr] = error / 16;
			next[i]     += (error*5) / 16;
			next[i+incr]+= (error*3) / 16;
		}
	}

	delete nextbuf;
	delete out;
	delete errors;
	delete level;
}

void DevBitmap::DevFloydSteinberg(WindowColorMap *wcm, DevBitmap *spr,
															ColorMapper *cmapper)
{
	RGB lasterror, error, *nextbuf, *next, rgb, result, v;
	register int i, x, y, incr;

	nextbuf= new RGB[size.x + 4];
	next= &nextbuf[2];

	for (y= 0; y < size.y; y++) {
		if (y & 1) {   // scan left
			incr= 1;
			i= size.x-1;
		} else {        // scan right
			incr= -1;
			i= 0;
		}
		for (x= 0; x < size.x; x++, i-= incr) {
			cmapper->Pixel2RGB(spr->GetPixel(i, y), &rgb);
			lasterror+= rgb;
			v= lasterror;
			v.Clamp();
			SetPixel(i, y, wcm->RGB2Index(&v, &result));
			error= rgb-result;
			lasterror= next[i-incr] + (error*7) / 16;
			next[i-incr] = error / 16;
			next[i]     += (error*5) / 16;
			next[i+incr]+= (error*3) / 16;
		}
	}

	delete nextbuf;
}

void DevBitmap::DevMapColors(DevBitmap *src, u_long *map)
{
	register int x, y;

	for (x= 0; x < size.x; x++)
		for (y= 0; y < size.y; y++)
			SetPixel(x, y, map[src->GetPixel(x, y)]);
}

void DevBitmap::DevMapDirect(WindowColorMap *wcm, DevBitmap *dbm, ColorMapper *cmapper)
{
	RGB rgb;
	register int x, y;
		
	for (x= 0; x < size.x; x++)
		for (y= 0; y < size.y; y++) {
			cmapper->Pixel2RGB(dbm->GetPixel(x, y), &rgb);
			SetPixel(x, y, wcm->RGB2Index(&rgb));
		}
}

DevBitmap *DevBitmap::FindInCache(WindowColorMap *wcm, const Point &scale)
{
	register int i;
	register BitmapCache *bmc;
	int wcmid= 0;

	if (wcm)
		wcmid= wcm->GetId();
	cacheclock++;

	for (i= 0; i < CACHESIZE; i++) {
		bmc= &Cache[(lastix + i) % CACHESIZE];
		if (bmc->org == this && scale == bmc->dbm->Size() && wcmid == bmc->wcmid) {
			bmc->clock= cacheclock;
			return bmc->dbm;
		}
	}
	return 0;
}

void DevBitmap::AddToCache(DevBitmap *dbm, WindowColorMap *wcm)
{
	register int i, old= cacheclock;
	register BitmapCache *bmc;

	for (lastix= i= 0; i < CACHESIZE; i++) {
		bmc= &Cache[i];
		if (bmc->clock < old) {
			old= bmc->clock;
			lastix= i;
		}
	}
	bmc= &Cache[lastix];

	DevBitmap *o1= bmc->org, *o2= bmc->dbm;
	bmc->dbm= bmc->org= 0;

	if (o1)
		o1->Unref();
	if (o2)
		o2->Unref();
		
	bmc->dbm= dbm; bmc->dbm->Ref();
	bmc->org= this; bmc->org->Ref();

	bmc->wcmid= wcm ? wcm->GetId() : 0;
	bmc->clock= cacheclock;
}

DevBitmap *DevBitmap::GetMask(const Point &e, ColorMapper *cmapper)
{
	DevBitmap *mask;

	if (!cmapper->HasMask())
		return 0;

	// try to find mask with correct size
	mask= FindInCache(0, e);
	if (mask)
		return mask;

	// try to find mask with original size
	mask= FindInCache(0, Size());
	if (mask == 0) {    // create mask from devbitmap
		mask= gWindowSystem->MakeDevBitmap(Size(), 0, 1);
		mask->Ref();
		int w= Size().x, h= Size().y;
		register int x, y;
		RGB c;
		for (y= 0; y < h; y++) {
			for (x= 0; x < w; x++) {
				cmapper->Pixel2RGB(GetPixel(x, y), &c);
				mask->SetPixel(x, y, c.alpha);
			}
		}
		AddToCache(mask, 0);
	}

	if (mask->Size() != e) {
		mask= mask->DevScaleBitmap(e);
		AddToCache(mask, 0);
	}

	return mask;
}

DevBitmap *DevBitmap::PrepareBitmap(WindowPort *port, const Point &e, ColorMapper *cmapper)
{
	register DevBitmap *bm;
	WindowColorMap *wcm= port->ColorMap();

	// try to find devbitmap with correct size and prepared for this port
	if (!changed && (bm= FindInCache(wcm, e)))
		return bm;
		
	// try to find devbitmap prepared for this port (but wrong size)
	bm= FindInCache(wcm, Size());
	if (bm == 0 || changed) {
		bm= cmapper->MapColors(wcm, this, port->GetDepth());
		if (bm)
			AddToCache(bm, wcm);
		else
			bm= this;
	}
	// now bm has same depth as port

	// but has it correct size ?
	if (bm->Size() != e) {
		bm= bm->DevScaleBitmap(e);
		AddToCache(bm, wcm);
	}

	changed= FALSE;

	return bm;
}

//---- abstract methods --------------------------------------------------------

DevBitmap *DevBitmap::DevAllocBitmap(const Point&, u_short)
{
	AbstractMethod("DevBitmap::DevAllocBitmap");
	return 0;
}

void DevBitmap::SetPixel(u_int, u_int, u_long)
{
	AbstractMethod("DevBitmap::SetPixel");
}

u_long DevBitmap::GetPixel(u_int, u_int)
{
	AbstractMethod("DevBitmap::GetPixel");
	return 0;
}

